﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//using System.Reflection;
using System.Data;
using System.Data.Entity;
//using System.Data.Entity.Core.EntityClient;
using System.Data.Entity.Core.Mapping;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Core.Objects;
using System.Data.Entity.Infrastructure;

using Seven.EntityBasic;
using Seven.MsSql.Annotations;
using Seven.MsSql.Edm;

namespace Seven.MsSql.Context
{
    /// <summary>
    /// 为 <see cref="System.Data.Entity.DbContext"/> 实例提供一组工具方法扩展。
    /// </summary>
    public static class EFDbContextExtensions
    {
        #region 内部的缓存定义

        private static Dictionary<Type, EntitySet[]> _entitySetsCache;
        private static Dictionary<Type, EntityTable[]> _entityTablesCache;
        private static Dictionary<Type, Dictionary<Type, EntityTable>> _entityTableCache;

        internal static Dictionary<Type, EntitySet[]> EntitySetsCache
        {
            get
            {
                if (_entitySetsCache == null)
                    _entitySetsCache = new Dictionary<Type, EntitySet[]>();
                return _entitySetsCache;
            }
        }

        internal static Dictionary<Type, EntityTable[]> EntityTablesCache
        {
            get
            {
                if (_entityTablesCache == null)
                    _entityTablesCache = new Dictionary<Type, EntityTable[]>();
                return _entityTablesCache;
            }
        }

        internal static Dictionary<Type, Dictionary<Type, EntityTable>> EntityTableCache
        {
            get
            {
                if (_entityTableCache == null)
                    _entityTableCache = new Dictionary<Type, Dictionary<Type, EntityTable>>();
                return _entityTableCache;
            }
        }

        #endregion

        /// <summary>
        /// 获取给定实体的 <see cref="DbEntityEntry(TEntity)"/> 对象，以便提供对与该实体有关的信息的访问以及对实体执行操作的功能。
        /// </summary>
        /// <typeparam name="TEntity">实体数据模型类型，对应当前数据库上下文中的一个表。</typeparam>
        /// <typeparam name="TKey">实体数据模型类型中的主键类型</typeparam>
        /// <param name="_this">dbcontext上下文</param>
        /// <param name="entity">实体对象副本，将根据此副本在 指定的dbcontext上下文 中查找原始实体对象</param>
        /// <param name="updateValues">是否将实体对象副本的数据更新到原始实体对象</param>
        /// <returns></returns>
        public static DbEntityEntry<TEntity> FindEntry<TEntity, TKey>(this System.Data.Entity.DbContext _this, TEntity entity, bool updateValues = true)
            where TKey : struct
            where TEntity : EntityBase<TKey>, new()
        {
            DbEntityEntry<TEntity> entry = _this.Entry(entity);
            if (entry.State == EntityState.Detached)
            {
                var entries = _this.ChangeTracker.Entries<TEntity>().Where(e => e != null && e.Entity != null).ToArray();
                if (entries.Length > 0)
                {
                    EntityTable table = GetEntityTable<TEntity, TKey>(_this);
                    if (table != null)
                    {
                        var properties = table.ModelType.KeyProperties;
                        var temp = entries.FirstOrDefault(e => properties.All(p => Object.Equals(e.Property(p.Name).CurrentValue, entry.Property(p.Name).CurrentValue)));

                        if (temp != null)
                        {
                            if (updateValues) { temp.CurrentValues.SetValues(entity); }
                            entry = temp;
                        }
                    }
                }
            }
            return entry;
        }

        /// <summary>
        /// 获取当前 实体数据模型 上下文中指定实体类型的数据库表映射关系。
        /// </summary>
        /// <param name="_this">实体数据上下文对象。</param>
        /// <param name="entityType">实体数据模型类型，对应当前数据库上下文中的一个表。</param>
        /// <returns>返回实体数据模型类型 <paramref name="entityType"/> 对应当前实体数据上下文对象中数据库的相应数据表的映射关系描述对象。</returns>
        public static EntityTable GetEntityTable(this System.Data.Entity.DbContext _this, Type entityType)
        {
            Type thisType = _this.GetType();
            Dictionary<Type, EntityTable> dict = null;
            EntityTable table = null;
            if (!EntityTableCache.TryGetValue(thisType, out dict))
            {
                dict = new Dictionary<Type, EntityTable>();
                EntityTableCache.Add(thisType, dict);
            }
            if (!dict.TryGetValue(entityType, out table))
            {
                table = GetTables(_this).FirstOrDefault(t => t.EntityType == entityType);
                dict.Add(entityType, table);
            }
            return table;
        }

        /// <summary>
        /// 获取当前 实体数据模型 上下文中指定实体类型的数据库表映射关系。
        /// </summary>
        /// <typeparam name="TEntity">实体数据模型类型，对应当前数据库上下文中的一个表。</typeparam>
        /// <typeparam name="TKey">实体数据模型类型中的主键类型</typeparam>
        /// <param name="_this">实体数据上下文对象。</param>
        /// <returns>返回实体数据模型类型 <typeparamref name="TEntity"/> 对应当前实体数据上下文对象中数据库的相应数据表的映射关系描述对象。</returns>
        public static EntityTable GetEntityTable<TEntity, Tkey>(this System.Data.Entity.DbContext _this)
            where Tkey : struct
            where TEntity : EntityBase<Tkey>, new()
        {
            return GetEntityTable(_this, typeof(TEntity));
        }

        /// <summary>
        /// 获取当前 实体数据模型 上下文中定义的所有 实体数据模型 类型所映射的数据表信息。
        /// </summary>
        /// <param name="_this">实体数据上下文对象。</param>
        /// <returns>返回当前 实体数据模型 上下文中定义的所有 实体数据模型 类型所映射的数据表信息。</returns>
        public static EntityTable[] GetTables(this System.Data.Entity.DbContext _this)
        {
            Type thisType = _this.GetType();
            EntityTable[] tables = null;
            if (!EntityTablesCache.TryGetValue(thisType, out tables))
            {
                MetadataWorkspace metadata = GetMetadataWorkspace(_this);
                ObjectItemCollection collection = metadata.GetItemCollection(DataSpace.OSpace) as ObjectItemCollection;

                EntityContainer container = metadata.GetItems<EntityContainer>(DataSpace.CSpace).FirstOrDefault();
                if (container == null)
                    throw new InvalidConstraintException("获取实体数据上下文对象中的 实体容器对象失败，无法获取其 EntityContainer。");

                var entitySets = container.EntitySets;
                var entitySetMappings = metadata.GetItems<EntityContainerMapping>(DataSpace.CSSpace).FirstOrDefault().EntitySetMappings;
                var entityTypes = metadata.GetItems<EntityType>(DataSpace.OSpace);

                List<EntityTable> list = new List<EntityTable>();
                foreach (var entityType in entityTypes)
                {
                    var elemType = collection.GetClrType(entityType);
                    if (elemType == null)
                        continue;

                    var entitySet = entitySets.First(s => s.ElementType.Name == entityType.Name);
                    var mapping = entitySetMappings.First(s => s.EntitySet == entitySet);
                    var mappingFragment = (mapping.EntityTypeMappings.FirstOrDefault(a => a.IsHierarchyMapping) ?? mapping.EntityTypeMappings.First()).Fragments.Single();

                    EntityTable table = new EntityTable();
                    table.ModelSet = entitySet;
                    table.StoreSet = mappingFragment.StoreEntitySet;
                    table.ModelType = entityType;
                    table.StoreType = mappingFragment.StoreEntitySet.ElementType;
                    table.EntityType = elemType;
                    table.TableName = entitySet.GetTableName();
                    table.Schema = entitySet.Schema;
                    list.Add(table);
                }
                tables = list.ToArray();
                EntityTablesCache.Add(thisType, tables);
            }
            return tables;
        }

        /// <summary>
        /// 获取当前 实体数据上下文 的 元数据工作区 对象。
        /// </summary>
        /// <param name="_this">实体数据上下文 对象。</param>
        /// <returns>返回当前 实体数据上下文 的 元数据工作区 对象。</returns>
        public static MetadataWorkspace GetMetadataWorkspace(this System.Data.Entity.DbContext _this)
        {
            return GetObjectContext(_this).MetadataWorkspace;
        }

        /// <summary>
        /// 获取当前 实体上下文 对象中所用的 对象上下文 <see cref="System.Data.Entity.Core.Objects.ObjectContext"/> 对象。
        /// </summary>
        /// <param name="_this">当前 实体上下文 对象。</param>
        /// <returns>返回一个对象上下文 <see cref="System.Data.Entity.Core.Objects.ObjectContext"/> 对象。</returns>
        public static ObjectContext GetObjectContext(this System.Data.Entity.DbContext _this)
        {
            return ((IObjectContextAdapter)_this).ObjectContext;
        }
    }
}
